package com.huilian.iotbox.admin.wap.security.jwt.filter;

import com.alibaba.fastjson.JSON;
import com.huilian.iotbox.admin.wap.security.config.CustomConfig;
import com.huilian.iotbox.admin.wap.security.enums.JwtRedisEnum;
import com.huilian.iotbox.admin.wap.security.jwt.util.JwtTokenUtil;
import com.huilian.iotbox.admin.wap.security.properties.WapSecurityProperties;
import com.huilian.iotbox.admin.wap.service.UserService;
import com.huilian.iotbox.data.common.RedisKey;
import com.huilian.iotbox.data.common.RedisLockKey;
import com.huilian.iotbox.data.common.ServerResponse;
import com.huilian.iotbox.data.enums.PermissionPlatformTypeEnum;
import com.huilian.iotbox.data.enums.RoleEnum;
import com.huilian.iotbox.data.po.Permission;
import com.huilian.iotbox.data.service.PermissionCommonService;
import com.huilian.iotbox.data.vo.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * JWT过滤器
 */
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserService userService;
    @Autowired
    private CustomConfig customConfig;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private WapSecurityProperties wapSecurityProperties;
    @Autowired
    private PermissionCommonService permissionCommonService;
    @Autowired
    private RedisTemplate<String, Serializable> redisCacheTemplate;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        log.info("请求路径：【{}】，请求方式为：【{}】", request.getRequestURI(), request.getMethod());
        if (antPathMatcher.match("/favicon.ico", request.getRequestURI())) {
            log.info("jwt不拦截此路径：【{}】，请求方式为：【{}】", request.getRequestURI(), request.getMethod());
            filterChain.doFilter(request, response);
            return;
        }
        if (checkIgnores(request)) {
            log.info("jwt不拦截此路径：【{}】，请求方式为：【{}】", request.getRequestURI(), request.getMethod());
            filterChain.doFilter(request, response);
            return;
        }

        if (!wapSecurityProperties.getJwt().isPreventsGetMethod()) {
            if (Objects.equals(RequestMethod.GET.toString(), request.getMethod())) {
                log.info("jwt不拦截此路径因为开启了不拦截GET请求：【{}】，请求方式为：【{}】", request.getRequestURI(), request.getMethod());
                filterChain.doFilter(request, response);
                return;
            }
        }

        // 获取Authorization
        String authHeader = request.getHeader("Authorization");
        if (StringUtils.isBlank(authHeader) || !authHeader.startsWith("Bearer ")) {
            log.error("Authorization的开头不是Bearer，Authorization===>【{}】 {}", authHeader, request.getRequestURI());
            responseEntity(response, HttpStatus.UNAUTHORIZED.value(), "尚未登录");
            return;
        }

        // 截取token
        final String authToken = authHeader.substring("Bearer ".length());
        final String randomKey = jwtTokenUtil.getMd5KeyFromToken(authToken);
        final String userId = jwtTokenUtil.getUsernameFromToken(authToken);
        log.info("randomKey：【{}】", randomKey);
        log.info("userId：【{}】", userId);

        // 验证token是否合法
        if (StringUtils.isBlank(userId) || StringUtils.isBlank(randomKey)) {
            log.info("userId【{}】或randomKey【{}】可能为null！", userId, randomKey);
            responseEntity(response, HttpStatus.UNAUTHORIZED.value(), "尚未登录");
            return;
        }

        // 验证token是否存在（过期了也会消失）
        final String token = (String) redisCacheTemplate.opsForValue().get(JwtRedisEnum.getTokenKey(userId, randomKey));
        if (Objects.isNull(token)) {
            log.info("Redis里没查到key【{}】对应的value！", JwtRedisEnum.getTokenKey(userId, randomKey));
            responseEntity(response, HttpStatus.UNAUTHORIZED.value(), "登录过期");
            return;
        }

        // 判断传来的token和存到redis的token是否一致
        if (!Objects.equals(token, authToken)) {
            log.error("前端传来的token【{}】和redis里的token【{}】不一致！", authToken, token);
            responseEntity(response, HttpStatus.UNAUTHORIZED.value(), "尚未登录");
            return;
        }

        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            String authenticationStr = (String) redisCacheTemplate.opsForValue().get(JwtRedisEnum.getAuthenticationKey(userId, randomKey));
            Authentication authentication = JSON.parseObject(authenticationStr, Authentication.class);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }


    private void responseEntity(HttpServletResponse response, Integer status, String msg) {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(status);
        try {
            response.getWriter().write(
                    JSON.toJSONString(ServerResponse.createByErrorCodeMessage("1", msg)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 请求是否不需要进行权限拦截
     *
     * @param request 当前请求
     * @return true - 忽略，false - 不忽略
     */
    private boolean checkIgnores(HttpServletRequest request) {
        String method = request.getMethod();

        HttpMethod httpMethod = HttpMethod.resolve(method);
        if (ObjectUtils.isEmpty(httpMethod)) {
            httpMethod = HttpMethod.GET;
        }

        Set<String> ignores = new HashSet<>();

        switch (httpMethod) {
            case GET:
                ignores.addAll(customConfig.getIgnores()
                        .getGet());
                break;
            case PUT:
                ignores.addAll(customConfig.getIgnores()
                        .getPut());
                break;
            case HEAD:
                ignores.addAll(customConfig.getIgnores()
                        .getHead());
                break;
            case POST:
                ignores.addAll(customConfig.getIgnores()
                        .getPost());
                break;
            case PATCH:
                ignores.addAll(customConfig.getIgnores()
                        .getPatch());
                break;
            case TRACE:
                ignores.addAll(customConfig.getIgnores()
                        .getTrace());
                break;
            case DELETE:
                ignores.addAll(customConfig.getIgnores()
                        .getDelete());
                break;
            case OPTIONS:
                ignores.addAll(customConfig.getIgnores()
                        .getOptions());
                break;
            default:
                break;
        }

        ignores.addAll(customConfig.getIgnores()
                .getPattern());

        if (CollectionUtils.isNotEmpty(ignores)) {
            for (String ignore : ignores) {
                AntPathRequestMatcher matcher = new AntPathRequestMatcher(ignore, method);
                if (matcher.matches(request)) {
                    return true;
                }
            }
        }

        return false;
    }
}
